<?php

class NCore_FilePort implements NCore_IPort
{
	private $name;
	private $valid = NULL;
	private $maxBytes = NULL;
	private $globalArray;
	private $globalArrayKey;
	private $mimeTypes = array();	
	private $uploadOptional = false;
	private $errorMsg = 'The file upload was invalid.';
    private $errorMsgMaxBytes = NULL;
    private $errorMsgMimeTypes = NULL;
    private $errorMsgUploadOptional = NULL;
	
	public function __construct($key)
	{
		$this->withName($key);
		$this->globalArrayKey = $key;
		$this->globalArray = $_FILES;	
	}
	public function getGlobalArray()
	{
		return $this->globalArray;
	}
	public function withGlobalArrayKey($key)
	{
		$this->globalArrayKey = $key;
		return $this;
	}
	public function getGlobalArrayKey()
	{
		return $this->globalArrayKey;
	}
    public function getSize()
    {
        return $this->globalArray[$this->globalArrayKey]['size'];
    }
    public function isPresent()
    {
        $ga = $this->getGlobalArray();
        if(!isset($ga[$this->getGlobalArrayKey()]))
        {
            return false;
        }

        return true;
    }
	public function isValid()
	{ 
		// more semantically appropriate wrapper		
		return $this->validate();
	}
	public function withName($name)
	{
		if(!preg_match(N::REGEX_KEY, $name))
		{
			throw new Exception('The name for port port \''.$this->getName().'\' is not valid.  Port names must begin with a letter; contain letters, numbers, or the underscore; and be no longer than 40 characters.');
		}

		$this->name = $name;
		return $this;
	}
	public function getName(){ return $this->name; }
    public function getFormattedName()
    {
        return str_replace('_', ' ', ucfirst($this->getName()));
    }
	public function withMaxBytes($maxBytes, $errorMsg)
	{
        if (!is_null($errorMsg))
        {
            $this->errorMsgMaxBytes = $errorMsg;
        }

		if($maxBytes > $this->returnBytes(ini_get('post_max_size')) || $maxBytes > $this->returnBytes(ini_get('upload_max_filesize')))
			throw new Exception('Please edit the values for post_max_size (currently set to '.$this->returnBytes(ini_get('post_max_size')).') and/or upload_max_filesize (currently set to '.$this->returnBytes(ini_get('upload_max_filesize')).') in your htaccess file so they are at least as large as the maxBytes set for file ports.');
		
		$this->maxBytes = $maxBytes;	
		return $this;
	}
	public function getMaxBytes(){ return $this->maxBytes; }
	public function returnBytes($val) {
	    $val = trim($val);
	    $last = strtolower(substr($val, -1));
	    switch($last) {
	        // The 'G' modifier is available since PHP 5.1.0
	        case 'g':
	            $val = substr($val, 0, strlen($val) - 1)*1073741824;
				break;
	        case 'm':
	            $val = substr($val, 0, strlen($val) - 1)*1048576;
				break;
	        case 'k':
	            $val = substr($val, 0, strlen($val) - 1)*1024;
				break;
			default:
				$val;
				break;
	    }
	
	    return $val;
	}
	public function withMimeTypes($types, $errorMsg)
	{
        if (!is_null($errorMsg))
        {
            $this->errorMsgMimeTypes = $errorMsg;
        }

		$this->mimeTypes = $types;
		return $this;
	}
	public function getMimeTypes(){ return $this->fileTypes; }
	public function withErrorMsg($msg)
	{
		$this->errorMsg = $msg;
		return $this;
	}
	public function getErrorMsg()
	{
		return $this->errorMsg;
	}
	public function withUploadOptional($bool, $errorMsg)
	{
        if (!is_null($errorMsg))
        {
            $this->errorMsgUploadOptional = $errorMsg;
        }

		$this->uploadOptional = $bool;
		return $this;
	}
	public function getUploadOptional()
	{
		return $this->uploadOptional;
	}
	public function getAllowNull(){return false;}
	public function validate()
	{
		if(is_null($this->valid))
		{					
			// check if there was even an upload
			if($this->globalArray[$this->globalArrayKey]['size'] < 1)
			{
				if($this->uploadOptional)
				{
					return $this->valid = true;
				}
				else
				{
                    $this->withErrorMsg($this->getErrorMsgUploadOptional());

					return $this->valid = false;
				}
			}
			
			if(!is_uploaded_file($this->globalArray[$this->globalArrayKey]['tmp_name']))
			{
                $this->withErrorMsg('\''.$this->getFormattedName().'\' does not appear to be an uploaded file.');

				return $this->valid = false;
			}

			// make sure somebody's not sending a sneaky path as part of the filename
			if(!preg_match(N::REGEX_FILE_NAME, $this->globalArray[$this->globalArrayKey]['name']))
            {
				$this->withErrorMsg('\''.$this->getFormattedName().'\' only accepts file names that contain letters, numbers, the dash (-), underscore (_), or spaces.');

                return $this->valid = false;
            }
				
			if($this->globalArray[$this->globalArrayKey]['size'] > $this->maxBytes)
            {
                $this->withErrorMsg($this->getErrorMsgMaxBytes());

				return $this->valid = false;
            }
				
			if(count($this->mimeTypes) > 0)
			{
				if(!in_array($this->globalArray[$this->globalArrayKey]['type'], $this->mimeTypes))
                {
                    $this->withErrorMsg($this->getErrorMsgMimeTypes());

					return $this->valid = false;
                }
			}
			else
			{
				throw new Exception('File uploads must have valid mime types (set via withMimeTypes()) for validation.');
			}
				
			if($this->globalArray[$this->globalArrayKey]['error'] != UPLOAD_ERR_OK)
            {
                $this->withErrorMsg('There was an error uploading the file.  Please try again.');
                
				return $this->valid = false;
            }
			
			$this->valid = true;
		}
	
		return $this->valid;
	}
	
	public function save($filename)
	{
		if(is_null($this->valid))
			throw new Exception('The file port \''.$this->getName().'\' must be validated before it can be saved.');
			
		if(!$this->valid)
			throw new Exception('The file port \''.$this->getName().'\' could not be saved because it was not valid.');
		
		if ($_FILES[$this->globalArrayKey]['size'] < 1) return;
		
		if(substr(trim($filename), 0, 1) != '/')
		{
			throw new Exception('The save() method of the file port \''.$this->getName().'\' expects a path syntax, for example \'/filename.jpeg\' or \'/home/username/public_html/uploads/filename.jpeg\'.');
		}
		
		if(is_null(N::config('file_ftp_url')))
		{
			if (!move_uploaded_file($_FILES[$this->globalArrayKey]['tmp_name'], $filename)) {
				throw new Exception('We were not able to save the file \''.$filename.'\'.');
			}
		}
		else
		{
			$localfile = $_FILES[$this->globalArrayKey]['tmp_name'];
			$fp = fopen($localfile, 'r');
			$ch = curl_init();
			
			curl_setopt($ch, CURLOPT_URL, N::config('file_ftp_url').$filename);
			curl_setopt($ch, CURLOPT_UPLOAD, 1);
			curl_setopt($ch, CURLOPT_INFILE, $fp);
			curl_setopt($ch, CURLOPT_INFILESIZE, $_FILES[$this->globalArrayKey]['size']);
			// needed so output isn't echo'd back
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
			curl_exec($ch);
			$error_no = curl_errno($ch);
			curl_close ($ch);
			if($error_no != 0) {
				if(is_int($error_no))
				{
					throw new Exception('There was an error uploading the file \''.$filename.'\'. CURL error number:'.$error_no);
				}
				else
				{
					throw new Exception('There was an error uploading the file, and the CURL error number was invalid, too.');
				}
			}
		}		
	}

    public function getErrorMsgMaxBytes()
    {
        if (!is_null($this->errorMsgMaxBytes)) return $this->errorMsgMaxBytes;

        return '\''.$this->getFormattedName().'\' accepts files that are no more than '.$this->getFriendlyMaxSize().'.';
    }

    public function getErrorMsgMimeTypes()
    {
        if (!is_null($this->errorMsgMimeTypes)) return $this->errorMsgMimeTypes;

        return '\''.$this->getFormattedName().'\' only accepts files of the following mime types: '.implode(',' ,$this->mimeTypes).'.';
    }

    public function getErrorMsgUploadOptional()
    {
        if (!is_null($this->errorMsgUploadOptional)) return $this->errorMsgUploadOptional;

        return '\''.$this->getFormattedName().'\' is a required upload.';
    }

    public function getFriendlyMaxSize()
    {
        if ($this->maxBytes < 1024)
        {
            return $this->maxBytes.' bytes';
        }
        if ($this->maxBytes < 1048576)
        {
            return ($this->maxBytes/1024).'KB';
        }
        if ($this->maxBytes < 1073741824)
        {
            return ($this->maxBytes/1048576).'MB';
        }

        return ($this->maxBytes/1073741824).'GB';
    }

	function __call($method, $args)
	{
		throw new Exception('There is no method called \''.$method.'\'().');
	}
}

?>